﻿// DEPENDENCIES:
//   Javascript
//   - jquery
//   - jquery.validate
//   - jquery.form
//   - jquery.metadata

using System.Collections.Generic;
using System.Text;

namespace BigfootWeb.Helpers
{

    /// <summary>
    /// This class helps us to create javascript code using server side code for commonly performed 
    /// operation. Although I do not like inline javascript, there are some times in the interested
    /// of productivity when it helps to make these items accessbile and repeatable. The funcitons  
    /// in this helper are particularly useful when using in conjunction with the HtmlHelper and
    /// tag builder classes.
    /// 
    /// It abstracts repetive tasks when making ajax calls, validating input, and adding functionality
    /// not currently present in asp.net in an easily repeatable manner.
    /// 
    /// Like most of the helpers this helper has a fluid interface as well. You can compose 
    /// functionality by calling methods on it before rendering the script.
    /// </summary>
    public class JSBuilder
    {

        /// <summary>
        /// Enumeration used when using jquery with ajax
        /// </summary>
        public enum DataTypeEnum
        {
            html,
            json,
            xml,
            script,
            jsonp,
            text
        }

        /// <summary>
        /// List of methods used on forms and jquery ajax calls
        /// </summary>
        public enum MethodEnum
        {
            @get,
            post
        }
        
        private StringBuilder _data = new StringBuilder();

        /// <summary>
        /// This is a static function to easily create a new JSHelper function inline
        /// </summary>
        public static JSBuilder Create()
        {
            return new JSBuilder();
        }

         /// <summary>
        /// Returns a properly formatted string to be include in Javascript, it also appends the quotes to the string
        /// this function also calls the CleanString to make sure any characters not allowed in javascript are safely scaped
        /// </summary>
        /// <param name="value">The string to clean without the beggining and ending literal quotes</param>
        public static string GetString(string value)
        {
            return string.Format("'{0}'", CleanString(value));
        }

        public static string CleanString(string value)
        {
            if (string.IsNullOrEmpty(value)) return value;

            var sb = new StringBuilder();
            foreach (var c in value)
            {
                switch (c)
                {
                    case '\"':
                        sb.Append("\\\"");
                        break;
                    case '\\':
                        sb.Append("\\\\");
                        break;
                    case '\b':
                        sb.Append("\\b");
                        break;
                    case '\f':
                        sb.Append("\\f");
                        break;
                    case '\n':
                        sb.Append("\\n");
                        break;
                    case '\r':
                        sb.Append("\\r");
                        break;
                    case '\t':
                        sb.Append("\\t");
                        break;
                    default:
                        var i = (int)c;
                        if (i < 32 || i > 127)
                        {
                            sb.AppendFormat("\\u{0:X04}", i);
                        }
                        else
                        {
                            sb.Append(c);
                        }
                        break;
                }
            }
            return sb.ToString();
        }

        /// <summary>
        /// Create an ajax post script
        /// </summary>
        /// <param name="url">The url to post to</param>
        /// <param name="callback">A JSHelper bilder class as the callback function</param>
        /// <param name="type">The type of data json... etc.</param>
        public JSBuilder Post(string url, JSBuilder callback, DataTypeEnum type)
        {
            return Post(url, null, callback, type);
        }

        /// <summary>
        /// Create an ajax post script sending some data
        /// </summary>
        /// <param name="url">The url to post to</param>
        /// <param name="data">JSObject with static data to post. The JSObject may also contain dynamic references to elements (i.e. jQuery("#item1").val())</param>
        /// <param name="callback">A JSHelper bilder class as the callback function</param>
        /// <param name="type">The type of data json... etc.</param>
        public JSBuilder Post(string url, JSObject data, JSBuilder callback, DataTypeEnum type)
        {
            return Ajax(MethodEnum.post, url, data, callback, type);
        }

        /// <summary>
        /// Create an ajax get script sending some data
        /// </summary>
        /// <param name="url">The url for the request</param>
        /// <param name="data">JSObject with static data to post. The JSObject may also contain dynamic references to elements (i.e. jQuery("#item1").val())</param>
        /// <param name="callback">A JSHelper bilder class as the callback function</param>
        /// <param name="type">The type of data json... etc.</param>
        public JSBuilder Get(string url, JSObject data, JSBuilder callback, DataTypeEnum type)
        {
            return Ajax(MethodEnum.get, url, data, callback, type);
        }

        /// <summary>
        /// Create an ajax script sending
        /// </summary>
        /// <param name="method">Specifies the method of the request post or get</param>
        /// <param name="url">The url for the request</param>
        /// <param name="data">JSObject with static data to post. The JSObject may also contain dynamic references to elements (i.e. jQuery("#item1").val())</param>
        /// <param name="callback">A JSHelper builder class as the callback function</param>
        /// <param name="type">The type of data json... etc.</param>
        public JSBuilder Ajax(MethodEnum method, string url, JSObject data, JSBuilder callback, DataTypeEnum type)
        {
            var d = string.Empty;
            if (data != null && data.IsEmpty() == false)
            {
                d = data.ToString();
            }
            return Ajax(method, url, d, callback, type);
        }

        /// <summary>
        /// Create an ajax script sending
        /// </summary>
        /// <param name="method">Specifies the method of the request post or get</param>
        /// <param name="url">The url for the request</param>
        /// <param name="data">This is json style object to post. The JSObject may also contain dynamic references to elements (i.e. jQuery("#item1").val())</param>
        /// <param name="callback">A JSHelper builder class as the callback function</param>
        /// <param name="type">The type of data json... etc.</param>
        public JSBuilder Ajax(MethodEnum method, string url, string data, JSBuilder callback, DataTypeEnum type)
        {

            // Post either provided values or the current form
            //   get request does not post the form
            var d = (method == MethodEnum.@get ? "null" : "bigfootWeb.getFormData()");
            if (string.IsNullOrEmpty(data) == false)
            {
                d = data;
            }

            // Specify the callback function
            var cb = "null";
            if (callback != null && callback.IsEmpty() == false)
            {
                cb = callback.ToString();
            }

            // Wrap into a function call
            return Wrap("{0} jQuery.{1}({2}, {3}, {4}, {5});", method.ToString(), GetString(url), d, cb, GetString(type.ToString()));

        }

        /// <summary>
        /// Show a message using the bigfootWeb.showGlobalNotice function 
        /// </summary>
        /// <param name="msg">Message to show</param>
        public JSBuilder ShowMessage(string msg)
        {
            return AddLine("bigfootWeb.showGlobalNotice({0})", GetString(msg));
        }

        /// <summary>
        /// Adds raw string to the JS builder. Be aware of line termination etc. As this does not do any of this for you
        /// it simple calls stringbuilder.append on the internal object. User other methods like AddLine to add a complete oneline statment to the script
        /// </summary>
        /// <param name="value">Javascript to add to the builder. i.e. jQuery("#test").addClass("newclass"); </param>
        public JSBuilder Add(string value)
        {
            _data.Append(value);
            return this;
        }

        /// <summary>
        /// Adds raw string to the JS builder using string.format. Be aware of line termination etc. As this does not do any of this for you
        /// it simple calls stringbuilder.append on the internal object. User other methods like AddLine to add a complete oneline statment to the script
        /// </summary>
        /// <param name="value">Javascript to add to the builder. i.e. jQuery("#test").addClass("{0}"); </param>
        /// <param name="params">List of values to use for the string.format</param>
        public JSBuilder Add(string value, params string[] @params)
        {
            _data.AppendFormat(value, @params);
            return this;
        }

        /// <summary>
        /// Adds a complete line of javascript including properly terminating the line with the semicollon
        /// </summary>
        /// <param name="line">Javascript line i.e. jQuery("test").addClass("one")</param>
        public JSBuilder AddLine(string line)
        {
            return Add(" " + line).EndLine();
        }

        /// <summary>
        /// Adds a complete line of javascript including properly terminating the line with the semicollon using string.format
        /// </summary>
        /// <param name="line">Javascript line i.e. jQuery("test").addClass("{0}")</param>
        /// <param name="params">The values to merge using string.format</param>
        public JSBuilder AddLine(string line, params string[] @params)
        {
            return Add(" " + line, @params).EndLine();
        }

        /// <summary>
        /// Adds a semicollon to the builder
        /// </summary>
        public JSBuilder EndLine()
        {
            return Add("; ");
        }

        /// <summary>
        /// Selects an element using jQuery and performs a command on it. 
        /// i.e. controlId="Test1" Command="addClass('one')" Outputs something like this jQuery('#Test1').addClass('one');
        /// </summary>
        /// <param name="controlID">The control id without the #</param>
        /// <param name="command">the command to execute on it i.e. addClass("one")</param>
        public JSBuilder SelectId(string controlID, string command)
        {
            return Add(JQHelper.Create().SelectID(controlID).AddCommand(command).ToString(true));
        }

        /// <summary>
        /// Selects an element using jQuery adds a class to it. Outpus: jQuery('#Test1').addClass('one');
        /// </summary>
        /// <param name="elementID">The element id without the #</param>
        /// <param name="className">The class name to add</param>
        public JSBuilder AddClass(string elementID, string className)
        {
            return SelectId(elementID, "addClass(" + className + ")").EndLine();
        }

        /// <summary>
        /// Selects an element using jQuery and removes a class from it. Outpus: jQuery('#Test1').removeClass('one');
        /// </summary>
        /// <param name="elementId">The element id without the #</param>
        /// <param name="classname">The class name to add</param>
        public JSBuilder RemoveClass(string elementId, string classname)
        {
            return SelectId(elementId, "removeClass(" + classname + ")").EndLine();
        }

        /// <summary>
        /// Determines if the script builder is empty, meaning no commands have been added ot it.
        /// </summary>
        public bool IsEmpty()
        {
            return _data.Length == 0;
        }

        /// <summary>
        /// Cancels events. Useful when creating inline functions for urls. outputs "return false; "
        /// </summary>
        public JSBuilder CancelEvent()
        {
            return AddLine("return false");
        }

        /// <summary>
        /// Wraps a validation check around the current script
        /// </summary>
        public JSBuilder WrapValidation()
        {
            const string format = "if (jQuery('form').validate({{ onsubmit: false, debug: false }}).form()) {{ {0} }}";
            return Wrap(format);
        }

        /// <summary>
        /// Wraps a validation check around the current script for the form specified
        /// </summary>
        public JSBuilder WrapFormValidation(string formName)
        {
            var format = "if (jQuery('#" + formName + "').validate({{ onsubmit: false, debug: false }}).form()) {{ {0} }}";
            return Wrap(format);
        }

        /// <summary>
        /// Wraps a validate check around the current script only for the input names specified
        /// </summary>
        public JSBuilder WrapValidation(params string[] inputNames)
		{
			//jQuery(formName).validate({{ onsubmit: false, debug: false }}).form();
			// if (jQuery('#addnote_note').valid() && jQuery('#addtag_tagname').valid()) { statements; }
			if (inputNames.Length > 0) {
				var namesToValidate = string.Empty;
				foreach (var name in inputNames) {
					if (namesToValidate != string.Empty)
						namesToValidate += " && ";
					namesToValidate += string.Format(" jQuery('#{0}').valid() ", name);
				}

				var wrapFormat = " jQuery('form').validate({{ onsubmit: false, debug: false }}).form();  " + " if (" + namesToValidate + ") {{ {0} }}";
				Wrap(wrapFormat);
			}
			return this;
		}

        /// <summary>
        /// Wraps a validation check around the current script for the group specified
        /// </summary>
        /// <param name="validationGroup">The group being validated</param>
        public JSBuilder WrapGroupValidation(string validationGroup)
        {
            var format = "if (ValidateGroup_" + validationGroup + "()) {{ {0} }}";
            return Wrap(format);
        }
        
        /// <summary>
        /// Wraps a confirmation check around the current script
        /// </summary>
        /// <param name="message">The message to use in the confirmation</param>
        /// <returns></returns>
        public JSBuilder WrapConfirm(string message)
        {
            const string format = "if (confirm({1})) {{ {0} }}";
            return Wrap(format, GetString(message));
        }

        /// <summary>
        /// Wraps the current script with the code specified
        /// </summary>
        /// <param name="format">The format to use. {0} is reserved for the current contents of the script.  
        /// i.e. if(value == {1}) {{ {0} }}; Use double quotes to escape { and }</param>
        /// <param name="params">Values to merge with your script.</param>
        /// <returns></returns>
        public JSBuilder Wrap(string format, params string[] @params)
        {
            // Create the parameter object {0} is reserved for the current string
            var newparams = new List<string> {_data.ToString()};
            if (@params.Length != 0)
            {
                newparams.AddRange(@params);
            }

            // Format the value
            var newvalue = string.Format(format, newparams.ToArray());

            // Replace existing value
            _data = new StringBuilder(newvalue);
            return this;
        }

        /// <summary>
        /// Returns the result of the built script
        /// </summary>
        public override string ToString()
        {
            return _data.ToString();
        }

        /// <summary>
        /// Returns the result of the built script. Same as ToString
        /// </summary>
        public string ToScript()
        {
            return ToString();
        }

        /// <summary>
        /// Returns the result of the built script in a way that is suitable for inline script. 
        /// i.e. javascript:yourscript;
        /// </summary>
        public string ToInline()
        {
            var script = ToString().TrimEnd();
            if (script.EndsWith(";") == false)
            {
                script += "; ";
            }
            return "javascript:" + script;
        }

    }

    #region "AjaxRequest"

    /// <summary>
    /// This helper class aids you in the creation of a script to execute an AJAX request.
    /// It goes beyond just the execution of the request, it also adds a host of things you can do
    /// once the request excutes successfully, like filling an element with the returned data or 
    /// removing alement from the dom. This class uses the same fluid interface as the other helpers
    /// so you can build on functionality like this .clearElement("elementid").updatePanel("elementid").focus("elementid")
    /// </summary>
    public class AjaxRequest
    {

        /// <summary>
        /// Factory method to creat an ajax request that will act against the supplied url
        /// </summary>
        /// <param name="url">Server url the ajax call uses</param>
        public static AjaxRequest Create(string url)
        {
            return Create(url, null);
        }

        /// <summary>
        /// Factory method to creat an ajax request that will act against the supplied url. It uses the String.Format method
        /// with the url to aid in the parametization of the url
        /// </summary>
        /// <param name="url">Server url the ajax call uses. String.Format will be called on this string</param>
        /// <param name="params">The values to merge with the url</param>
        /// <returns>A new AjaxRequest object</returns>
        public static AjaxRequest Create(string url, params string[] @params)
        {
            var obj = new AjaxRequest
                          {
                              _url = @params != null ? string.Format(url, @params) : url
                          };
            return obj;
        }

        // These are the corresponding private methods of the class
        private string _url;
        private string _updatepanel;
        private string _removeElement;
        private string _confirmMessage;
        private string _successMessage;
        private string _callback;
        private bool _validate;
        private JSObject _postData = new JSObject();
        private JSBuilder.MethodEnum _method = JSBuilder.MethodEnum.@get;
        private JSBuilder.DataTypeEnum _dataType = JSBuilder.DataTypeEnum.html;
        private string _redirectToUrl;
        private string _clearElement;
        private string _clearInputs;
        private string _loadUrl;
        private string _loadElement;
        private string _hideElement;
        private string _showElement;
        private string _removeClassElement;
        private string _removeClassNames;
        private string _focus;
        private string _partialForm;
        private bool _clearForm;


        /// <summary>
        /// Sumits the inputs of the partial form specified
        /// </summary>
        /// <param name="containerId">The partial form element id</param>
        public AjaxRequest PartialForm(string containerId)
        {
            _partialForm = containerId;
            return this;
        }

        /// <summary>
        /// Sets the innerHtml value of the element specified with the data returned from the ajax request
        /// </summary>
        /// <param name="name">Element id that will hold the contents</param>
        public AjaxRequest UpdatePanel(string name)
        {
            _updatepanel = name;
            return this;
        }

        //Public Function UpdateRouteContent() As AjaxRequest
        //    _updatepanel = "RouteContentContainer"
        //    Return Me
        //End Function

        /// <summary>
        /// Removes this element once the ajax request completes
        /// </summary>
        /// <param name="name">Element id</param>
        public AjaxRequest RemoveElement(string name)
        {
            _removeElement = name;
            return this;
        }

        /// <summary>
        /// Redirects the user to the url specified once the ajax request completes
        /// </summary>
        /// <param name="url">Url to redirect the user to client side</param>
        public AjaxRequest RedirectToUrl(string url)
        {
            _redirectToUrl = url;
            return this;
        }

        /// <summary>
        /// Sets the focus to the element specified once the ajax request completes
        /// </summary>
        /// <param name="elementId">Element id</param>
        public AjaxRequest focus(string elementId)
        {
            _focus = elementId;
            return this;
        }

        /// <summary>
        /// Adds a confirmation message before the ajax request happens.
        /// </summary>
        /// <param name="message">Text to display to the user in the confirmaiton message</param>
        public AjaxRequest ConfirmMessage(string message)
        {
            _confirmMessage = message;
            return this;
        }

        /// <summary>
        /// Adds a success message once the ajax request completes
        /// </summary>
        /// <param name="message">Message text to display</param>
        public AjaxRequest SuccessMessage(string message)
        {
            _successMessage = message;
            return this;
        }

        /// <summary>
        /// Calls the supplied function or executes the supplied script once the ajax request completes
        /// </summary>
        /// <param name="function">This could be any valid javscript, either ExecuteOnSuccess() or some actual script like function(data){ alert(data); } </param>
        public AjaxRequest Callback(string function)
        {
            _callback = function;
            return this;
        }

        /// <summary>
        /// Posts the specified JSObject literal values
        /// </summary>
        /// <param name="obj">The JSObject to post</param>
        public AjaxRequest PostData(JSObject obj)
        {
            _postData.Merge(obj);
            return this;
        }

        /// <summary>
        /// Validates the request vefore sumission
        /// </summary>
        public AjaxRequest Validate()
        {
            _validate = true;
            return this;
        }
        
        /// <summary>
        /// Adds some literal data to the post data
        /// </summary>
        /// <param name="param">The parameter name for the data</param>
        /// <param name="value">The value to post</param>
        public AjaxRequest AddData(string param, string value)
        {
            _postData.Add(param, value);
            return this;
        }

        /// <summary>
        /// Adds some literal data to the post data. While also properly escaping and adding quotes around the string value
        /// </summary>
        /// <param name="param">The parameter name for the data</param>
        /// <param name="value">The value to post. This value will be properly escaped and quoted to be included in the script</param>
        public AjaxRequest AddDataString(string param, string value)
        {
            _postData.AddString(param, value);
            return this;
        }

        /// <summary>
        /// Determins the method of the request, post / get
        /// </summary>
        public AjaxRequest Method(JSBuilder.MethodEnum ajaxMethod)
        {
            _method = ajaxMethod;
            return this;
        }
        
        /// <summary>
        /// Determines the datatype to be expected as the ajax response
        /// </summary>
        public AjaxRequest DataType(JSBuilder.DataTypeEnum type)
        {
            _dataType = type;
            return this;
        }
        
        /// <summary>
        /// Signals that the request will be doing using the GET http method
        /// </summary>
        public AjaxRequest Get()
        {
            return Method(JSBuilder.MethodEnum.@get);
        }

        /// <summary>
        /// Signals that the request will be doing using the GET http method
        /// </summary>
        public AjaxRequest Post()
        {
            return Method(JSBuilder.MethodEnum.post);
        }

        /// <summary>
        /// Removes the content of the specified element once the ajax request completes
        /// </summary>
        public AjaxRequest ClearElement(string elementId)
        {
            _clearElement = JQHelper.GetElementID(elementId);
            return this;
        }
        
        /// <summary>
        /// Will clear the value of the specified inputs once the ajax request completes. You can specify multiple inputs separated by a comma
        /// </summary>
        /// <param name="inputIds">The imputs to clear separated by a comma. i.e. name,lastname,city,state do not include the # seclector in front</param>
        public AjaxRequest ClearInput(string inputIds)
        {
            _clearInputs = inputIds;
            return this;
        }
        
        /// <summary>
        /// Once the ajax request completes. It will execute another ajax request to load the contents of the url into the element
        /// </summary>
        /// <param name="url">Element to request the contents to be loaded</param>
        /// <param name="elementid">Element id to load with the contents</param>
        public AjaxRequest Load(string url, string elementid)
        {
            _loadUrl = url;
            _loadElement = JQHelper.GetElementID(elementid);
            return this;
        }

        /// <summary>
        /// Hides the element on ajax complete
        /// </summary>
        public AjaxRequest HideElement(string ElementID)
        {
            _hideElement = JQHelper.GetElementID(ElementID);
            return this;
        }
        
        /// <summary>
        /// Shows the elmenet on ajax complete
        /// </summary>
        public AjaxRequest ShowElement(string ElementID)
        {
            _showElement = JQHelper.GetElementID(ElementID);
            return this;
        }

        /// <summary>
        /// Hides one element and show another on ajax complete
        /// </summary>
        public AjaxRequest HideAndShowElement(string HideElementID, string ShowElementID)
        {
            _hideElement = JQHelper.GetElementID(HideElementID);
            _showElement = JQHelper.GetElementID(ShowElementID);
            return this;
        }

        /// <summary>
        /// Removes a class from an elmenet on ajax complete
        /// </summary>
        public AjaxRequest RemoveClass(string ElementID, string ClassNames)
        {
            _removeClassElement = JQHelper.GetElementID(ElementID);
            _removeClassNames = ClassNames;
            return this;
        }

        /// <summary>
        /// Clears the content of the form on ajax complete
        /// </summary>
        public AjaxRequest ClearForm()
        {
            _clearForm = true;
            return this;
        }

        /// <summary>
        /// Creates the callback function to execute once the ajax request completes with all the commands added to the request
        /// </summary>
        private JSBuilder ToCallbackFunction()
        {
            var callback = JSBuilder.Create();
            // Update Panel
            if (string.IsNullOrEmpty(_updatepanel) == false)
            {
                callback.SelectId(_updatepanel, "html(data)");
            }
            // Remove Element
            if (string.IsNullOrEmpty(_removeElement) == false)
            {
                callback.SelectId(_removeElement, "remove()");
            }
            // Hide Element
            if (string.IsNullOrEmpty(_hideElement) == false)
            {
                callback.SelectId(_hideElement, "hide()");
            }
            // Show Element
            if (string.IsNullOrEmpty(_showElement) == false)
            {
                callback.SelectId(_showElement, "show()");
            }
            // Remove classes
            if (string.IsNullOrEmpty(_removeClassElement) == false && string.IsNullOrEmpty(_removeClassNames) == false)
            {
                callback.SelectId(_removeClassElement, "removeClass(" + JSBuilder.GetString(_removeClassNames) + ")");
            }
            // Callback function
            if (string.IsNullOrEmpty(_callback) == false)
            {
                callback.AddLine(_callback);
            }
            // Show completed message
            if (string.IsNullOrEmpty(_successMessage) == false)
            {
                callback.ShowMessage(_successMessage);
            }
            // Clear the form
            if (_clearForm & string.IsNullOrEmpty(_partialForm) == false)
            {
                callback.AddLine(JQ().SelectFields(_partialForm).AddCommand("clearFields()").ToString());
                callback.AddLine(JQ().Select("form .error:not(label.error)").AddCommand("removeClass()").ToString());
                callback.AddLine(JQ().Select("label.error").AddCommand("remove()").ToString());
            }
            else if (_validate)
            {
                callback.AddLine("jQuery('form .error').removeClass()");
                callback.AddLine("jQuery('label.error').remove()");
            }
            // Clear and hide an element
            if (string.IsNullOrEmpty(_clearElement) == false)
            {
                callback.AddLine("jQuery('" + _clearElement + "').html('').hide()");
            }
            // Clear inputs
            if (string.IsNullOrEmpty(_clearInputs) == false)
            {
                foreach (var i in _clearInputs.Split(','))
                {
                    if (i.Trim().Length == 0)
                        continue;
                    callback.AddLine("jQuery('#" + i.Trim() + "').val('')");
                }
            }
            // Set the focus
            if (string.IsNullOrEmpty(_focus) == false)
            {
                callback.SelectId(_focus, "focus()");
            }
            // Redirect to url
            if (string.IsNullOrEmpty(_redirectToUrl) == false)
            {
                callback.AddLine(" if (bigfootWeb.errorHappened == false) window.location = " + JSBuilder.GetString(_redirectToUrl));
            }
            // Load data into element
            if (string.IsNullOrEmpty(_loadUrl) == false && string.IsNullOrEmpty(_loadElement) == false)
            {
                callback.AddLine(" js.Add(\"jQuery('" + _loadElement + "').load('" + JSBuilder.CleanString(_loadUrl) + "').show()");
            }

            return callback.Wrap(" function(data) {{ {0} }} ");
        }

        /// <summary>
        /// Creates the script to be outputed to the page
        /// </summary>
        public override string ToString()
        {
            var script = JSBuilder.Create();

            string data;

            // Get the data to post
            if (_postData.IsEmpty() & string.IsNullOrEmpty(_partialForm))
            {
                _postData.AddString("emptyPost", "empty");
            }

            // Set the data to be posted to the JSON object
            data = _postData.ToString();

            // When posting a partial form then set the data to the serialized fields
            if (string.IsNullOrEmpty(_partialForm) == false)
            {
                data = string.Format(JQ().SelectFields(_partialForm).AddCommand("fieldSerialize()").ToString());
            }

            // Crete the AJAX request
            script.Ajax(_method, _url, data, ToCallbackFunction(), _dataType);

            // Wrap the ajax call with confirm
            if (string.IsNullOrEmpty(_confirmMessage) == false)
            {
                script.WrapConfirm(_confirmMessage);
            }

            // VALIDATION
            if (_validate & string.IsNullOrEmpty(_partialForm) == false)
            {
                script.WrapGroupValidation(_partialForm);
            }

            // Return false
            script.Add("return false; ");

            // Return the script
            return script.ToString();
        }

        /// <summary>
        /// Static function to create JQHelper build simple jQuery commands. Used throughout the JSHelper class.
        /// </summary>
        public static JQHelper JQ()
        {
            return JQHelper.Create();
        }

    }
    #endregion


    #region "AjaxForm"

    /// <summary>
    /// Creates the script necessary to Ajaxify an html form. It wraps the jquery.form
    /// </summary>
    public class AjaxForm
    {

        private string _formName = "";
        private string _url = "";
        private bool _validate = true;
        private string _type = "POST";
        // xml || json || script || null (default)
        private string _dataType = "";
        // element to be udpated
        private string _target = "";
        // Determine weather to replace the contents of the target or the whole target element
        private bool _replaceTarget;
        private string _beforeSerialize = "";
        private string _beforeSubmit = "";
        private string _success = "";
        // default = null
        private bool _resetForm;
        // default = null
        private bool _clearForm;
        private bool _wrapOnReady;

        private bool _wrapScriptTag;
        public AjaxForm()
        {
            _clearForm = false;
        }

        /// <summary>
        /// Factory method to ajaxify the form
        /// </summary>
        /// <param name="formName">The name of the form</param>
        /// <param name="url">The url the form will be posted to</param>
        public static AjaxForm Create(string formName, string url)
        {
            return new AjaxForm
            {
                _formName = formName,
                _url = url
            };
        }

        /// <summary>
        /// Cancels any validation from happening when submitting the form
        /// </summary>
        public AjaxForm DontValidate()
        {
            _validate = false;
            return this;
        }

        /// <summary>
        /// Sets the ajax request to be a GET or POST
        /// </summary>
        /// <param name="value">GET or POST</param>
        public AjaxForm RequestType(string value)
        {
            _type = value;
            return this;
        }

        /// <summary>
        /// Specifies the dataType being returned from the ajax request
        /// </summary>
        /// <param name="value">xml || json || script || null (which is the default as signals it that is html)</param>
        public AjaxForm DataType(string value)
        {
            _dataType = value;
            return this;
        }


        /// <summary>
        /// Specifies the datatype being returned from the ajax request as bing json
        /// </summary>
        public AjaxForm json()
        {
            return DataType("json");
        }

        /// <summary>
        /// Replaces the content of this element id with the data returned from the request
        /// </summary>
        public AjaxForm Target(string elementId)
        {
            _target = elementId;
            return this;
        }

        /// <summary>
        /// Replaces the content of this element id with the data returned from the request. 
        /// When replace target = false, then it appends the data instead of replacing it which is the default behaviour
        /// </summary>
        public AjaxForm Target(string elementId, bool replaceTarget)
        {
            _target = elementId;
            _replaceTarget = replaceTarget;
            return this;
        }

        /// <summary>
        /// Calls this function or javascript before the form is serialized. Allows you to do some preprocessing on the form
        /// </summary>
        /// <param name="script">function() or actual javascript to run like function(){ statements; }</param>
        public AjaxForm BeforeSerialize(string script)
        {
            _beforeSerialize = script;
            return this;
        }

        /// <summary>
        /// Calls this function or javascript before the form is submitted. Allows you to do some preprocessing on the form before it is submitted
        /// </summary>
        /// <param name="script">function() or actual javascript to run like function(){ statements; }</param>
        public AjaxForm BeforeSubmit(string script)
        {
            _beforeSubmit = script;
            return this;
        }

        /// <summary>
        /// Calls this function or javascript after the ajax request completes successfully
        /// </summary>
        /// <param name="script">function() or actual javascript to run like function(){ statements; }</param>
        public AjaxForm Success(string script)
        {
            _success = script;
            return this;
        }

        /// <summary>
        /// Clears the form on ajax complete
        /// </summary>
        public AjaxForm ClearForm()
        {
            _clearForm = true;
            return this;
        }

        /// <summary>
        /// Resets the form on ajax complete
        /// </summary>
        public AjaxForm ResetForm()
        {
            _resetForm = true;
            return this;
        }

        /// <summary>
        /// Wraps the script created with this interface in a jQuery onready event to be outputed to the page
        /// </summary>
        public AjaxForm OnReady()
        {
            return OnReady(false);
        }

        /// <summary>
        /// Wraps the script created with this interface in a jQuery onready event to be outputed to the page, and it determines weather to
        /// add the script tag to the output once you call ToString
        /// </summary>
        public AjaxForm OnReady(bool addScriptTag)
        {
            _wrapOnReady = true;
            _wrapScriptTag = addScriptTag;
            return this;
        }

        /// <summary>
        /// Adds the script tag to the script output from this class once you call ToString
        /// </summary>
        public AjaxForm AddScriptTag()
        {
            _wrapScriptTag = true;
            return this;
        }

        /// <summary>
        /// Grabs all the values and options specified and creates the proper script to ouput to the page. If you called ton OnReady it outputs the script
        /// within a jQuery on document ready function, if you flagged it to AddScriptTag it sorrounds the whole script with the propery <script></script> tag
        /// </summary>
        /// <returns>Properly formatted javascript to include in the page</returns>
        public override string ToString()
        {
            var options = new JSObject();
            options.AddString("url", _url);
            options.AddString("type", _type);
            if (!string.IsNullOrEmpty(_dataType))
            {
                options.AddString("dataType", _dataType);
            }
            if (!string.IsNullOrEmpty(_target))
            {
                options.AddString("target", _target);
                if (_replaceTarget)
                {
                    options.Add("replaceTarget", "true");
                }
            }
            if (!string.IsNullOrEmpty(_beforeSerialize) & _validate == false)
            {
                options.Add("beforeSerialize", _beforeSerialize);
            }
            else if (!string.IsNullOrEmpty(_beforeSerialize) & _validate)
            {
                options.Add("beforeSerialize", "function(){ " + _beforeSerialize + "(); if (!jQuery('#" + _formName + "').validate().form()) return false; }");
            }
            else if (_validate)
            {
                options.Add("beforeSerialize", "function() { if (!jQuery('#" + _formName + "').validate().form()) return false; }");
            }
            if (!string.IsNullOrEmpty(_beforeSubmit))
            {
                options.Add("beforeSubmit", _beforeSubmit);
            }
            if (!string.IsNullOrEmpty(_success))
            {
                options.Add("success", _success);
            }
            if (_resetForm)
            {
                options.Add("resetForm", "true");
            }
            if (_clearForm)
            {
                options.Add("clearForm", "true");
            }

            var script = "";

            // Add the script tag if requested
            if (_wrapScriptTag)
                script += "<script type='text/javascript'>";
            // Add the jquery on ready tag
            if (_wrapOnReady)
                script += "jQuery(function(){";
            script += "jQuery('#" + _formName + "').ajaxForm(" + options + ");";
            // Close the jquery on ready tag
            if (_wrapOnReady)
                script += "});";
            // Close the script onready tag
            if (_wrapScriptTag)
                script += "</script>";

            return script;
        }

    }

    #endregion


    #region "JSObject"

    /// <summary>
    /// This class is a helper class to build a JSON string. It is very useful when generating script server side.
    /// </summary>
    public class JSObject
    {
        
        /// <summary>
        /// Specifies the type of value you are adding to the JSObject. When other the value is not escaped or quoted as you 
        /// would have to do for a string.Y ou can nest JSObjects by adding a property to it, and passing as the value the 
        /// ToString method call of another JSObject
        /// </summary>
        public enum ValueTypeEnum
        {
            String,
            Other
        }

        /// <summary>
        /// Factory method to create a JSObject
        /// </summary>
        public static JSObject Create()
        {
            return new JSObject();
        }


        private Dictionary<string, string> _data = new Dictionary<string, string>();

        /// <summary>
        /// Adds a property to the json object. It asumes that it is not a string so it ouputs the value directly. 
        /// if the value is empty then it oupts null as the value for the property. You can nest JSObjects by
        /// adding a property to it, and passing as the value the ToString method call of another JSObject
        /// </summary>
        /// <param name="name">Property name {name}: {value}</param>
        /// <param name="value">Property value</param>
        public JSObject Add(string name, string value)
        {
            if (value == string.Empty)
            {
                value = "null";
            }
            return Add(name, value, ValueTypeEnum.Other);
        }

        /// <summary>
        /// Adds a property to the json object as a string so it properly escapes it and quotes it 
        /// </summary>
        /// <param name="name">Property name {name}: {value}</param>
        /// <param name="value">Property value string will be escaped and quoted</param>
        public JSObject AddString(string name, string value)
        {
            return Add(name, value, ValueTypeEnum.String);
        }

        /// <summary>
        /// Adds a property to the json object. If the valuetype is a string it is escaped and quoted. If it is not it ouputs the value directly. 
        /// if the value is empty then it oupts null as the value for the property
        /// </summary>
        /// <param name="name">Property name {name}: {value}</param>
        /// <param name="value">Property value. string will be escaped and quoted</param>
        /// <param name="ValueType">The type of value being added. If a string, it will be escaped and quoted</param>
        public JSObject Add(string name, string value, ValueTypeEnum ValueType)
        {
            if (ValueType == ValueTypeEnum.String) value = JSBuilder.GetString(value);
            Data.Add(name, value);
            return this;
        }

        /// <summary>
        /// This is the list of properties will start being outputed with the object
        /// </summary>
        public Dictionary<string, string> Data
        {
            get { return _data; }
        }

        /// <summary>
        /// Merges another JSObject into this one
        /// </summary>
        public JSObject Merge(JSObject obj)
        {
            foreach (var key in obj.Data.Keys)
            {
                _data.Add(key, obj.Data[key]);
            }
            return this;
        }

        /// <summary>
        /// Counts how many properties have been added to this json object
        /// </summary>
        public int Count()
        {
            return _data.Count;
        }

        /// <summary>
        /// Determines if no data has been added to the object
        /// </summary>
        /// <returns></returns>
        public bool IsEmpty()
        {
            return Count() == 0;
        }

        /// <summary>
        /// Properly oupts the JSON sorrounded by { } braces
        /// </summary>
        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.Append(" { ");
            var first = true;
            foreach (var key in _data.Keys)
            {
                if (first == false)
                {
                    sb.Append(", ");
                }
                first = false;
                sb.AppendFormat("{0}: {1}", key, _data[key]);
            }
            sb.Append(" } ");
            return sb.ToString();
        }
    }
    #endregion


    #region "JQHelper"

    /// <summary>
    /// This is a simple class to aid us in the creation of jQuery scripts
    /// </summary>
    public class JQHelper
    {
        
        private readonly StringBuilder builder = new StringBuilder();

        /// <summary>
        /// Determines if the script is empty, meaning no commands have been added to it
        /// </summary>
        public bool IsEmpty()
        {
            return builder.ToString() == string.Empty;
        }

        /// <summary>
        /// Factory method to create a new JQHelper
        /// </summary>
        public static JQHelper Create()
        {
            return new JQHelper();
        }

        /// <summary>
        /// Selects a set of elements using a jquery selector. If the JQHelper has other 
        /// commands and the script is not ended with a ; it adds it before adding this statement
        /// Appends: [;]jQuery('selector')
        /// </summary>
        /// <param name="selector">Any jQuery selector</param>
        public JQHelper Select(string selector)
        {
            if (IsEmpty() == false && builder.ToString().TrimEnd().EndsWith(";") == false)
            {
                builder.Append(";");
            }
            builder.AppendFormat("jQuery('{0}')", selector);
            return this;
        }

        /// <summary>
        /// Selects a set of elements using a jquery element id selector. If the JQHelper has other 
        /// commands and the script is not ended with a ; it adds it before adding this statement
        /// Appends: [;]jQuery('#selector')
        /// </summary>
        /// <param name="selector">And element id selector with or without the #</param>
        public JQHelper SelectID(string selector)
        {
            return Select(GetElementID(selector));
        }

        /// <summary>
        /// Selects all input elements within the contianer except file inputs. If the JQHelper has other 
        /// commands and the script is not ended with a ; it adds it before adding this statement
        /// Appends: [;]jQuery('#container :input, #container select, #container textarea')
        /// </summary>
        /// <param name="container">The elementid id selector that contains the inputs with or without the #</param>
        public JQHelper SelectFields(string container)
        {
            return Select(string.Format("#{0} :input, #{0} select, #{0} textarea", container));
        }

        /// <summary>
        /// Adds a class to the select element(s). 
        /// Appends: [.]addClass('className')
        /// </summary>
        /// <param name="className">Class name to add</param>
        public JQHelper addClass(string className)
        {
            return AddCommand("addClass({0})", GetString(className));
        }

        /// <summary>
        /// Removes a class from the select element(s). 
        /// Appends: [.]removeClass('className')
        /// </summary>
        /// <param name="className">Class name to add</param>
        public JQHelper removeClass(string className)
        {
            return AddCommand("removeClass({0})", GetString(className));
        }

        /// <summary>
        /// Shows the selected element(s)
        /// Appends: [.]show()
        /// </summary>
        public JQHelper show()
        {
            return AddCommand("show()");
        }

        /// <summary>
        /// Hides the selected element(s)
        /// Appends: [.]hide()
        /// </summary>
        public JQHelper hide()
        {
            return AddCommand("hide()");
        }

        /// <summary>
        /// Toggles the visiblity of the selected element(s)
        /// Appends: [.]toggle()
        /// </summary>
        public JQHelper toggle()
        {
            return AddCommand("toggle()");
        }

        /// <summary>
        /// Adds or removes a class when not found from the selected element(s)
        /// Appends: [.]toggleClass('className')
        /// </summary>
        public JQHelper toggleClass(string className)
        {
            return AddCommand("toggleClass(" + JSBuilder.GetString(className) + ")");
        }

        /// <summary>
        /// Removes the html conents and hides the selected element(s)
        /// Appends: [.]html('').hide()
        /// </summary>
        public JQHelper clear()
        {
            AddCommand("html('')");
            AddCommand("hide()");
            return this;
        }

        /// <summary>
        /// Sets the focus to an element id
        /// Appends: [;]jQuery('#selector').focus()
        /// </summary>
        public JQHelper setfocus(string elementid)
        {
            return SelectID(elementid).focus();
        }

        /// <summary>
        /// Set the focus for the selected element
        /// Appends: [.]focus()
        /// </summary>
        public JQHelper focus()
        {
            return AddCommand("focus()");
        }

        //Public Function onEnterTrigger(ByVal elementId As String, ByVal triggerElementId As String, ByVal eventName As String, ByVal cancelEvent As Boolean) As JQHelper
        //    SelectID(elementId)

        //    Dim cmd = New StringBuilder
        //    cmd.Append("keyup(function(event) { ")
        //    cmd.Append("    if (event.keyCode == 13) {")
        //    cmd.AppendFormat(" jQuery('{0}').trigger(jQuery.Event({1}));", GetElementID(triggerElementId), GetString(eventName))
        //    If cancelEvent Then
        //        cmd.Append("    return false; ")
        //    End If
        //    cmd.Append(" }})")

        //    Return AddCommand(cmd.ToString)
        //End Function

        /// <summary>
        /// Adds a command to the selected element(s). It uses the String.Format to merge command and possible parameters
        /// Appends: [.]command('with possible values')
        /// i.e. AddCommand("addClass('{0}')", "class1 class2") appends [.]addClass('class1 class2')
        /// </summary>
        /// <param name="statement">The command name with possible String.Format {} braces to merge with the params</param>
        /// <param name="params">Values to merge with the command. When left empty then nothing is merged</param>
        public JQHelper AddCommand(string statement, params string[] @params)
        {
            if (builder.ToString().EndsWith(".") == false)
                builder.Append(".");
            if (@params != null && @params.Length > 0)
            {
                builder.AppendFormat(statement, @params);
            }
            else
            {
                builder.Append(statement);
            }
            return this;
        }

        /// <summary>
        /// Returns a scaped and properly quoted string for inclussion in javascript
        /// </summary>
        /// <param name="Data">The string to escape and quote</param>
        private string GetString(string Data)
        {
            return JSBuilder.GetString(Data);
        }

        /// <summary>
        /// Retrives an element id jquery selector. It adds the # at the beggining of the element id if not there
        /// </summary>
        public static string GetElementID(string elementid)
        {
            elementid = elementid.Trim();
            if (elementid.StartsWith("#") == false)
            {
                elementid = "#" + elementid;
            }
            return elementid;
        }

        /// <summary>
        /// Outpus the script ready for inclussion in a page. Does not output the script tag or the jquery on ready by default.
        /// it also does not add a semicolon at the end by default
        /// </summary>
        public override string ToString()
        {
            return ToString(false);
        }
        
        /// <summary>
        /// Outpus the script ready for inclussion in a page. Does not output the script tag or the jquery on ready by default.
        /// it also adds a semicolon at the end if requted and it isn't there
        /// </summary>
        public string ToString(bool AddSemicolonAtTheEnd)
        {
            if (AddSemicolonAtTheEnd && builder.ToString().Trim().EndsWith(";") == false)
            {
                builder.Append(";");
            }
            return builder.ToString();
        }

        public string ToScriptBlock()
        {
            var script = new StringBuilder();
            script.Append(" <script type='text/javascript'> ");
            script.Append("jQuery(document).ready(function(){ ");
            script.Append("   " + ToString(true));
            script.Append(" }); ");
            script.Append(" </script>");
            return script.ToString();
        }

    }
    #endregion

}